﻿// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证（版本 2.0）进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动！任何基于本项目二次开发而产生的一切法律纠纷和责任，我们不承担任何责任！

using Microsoft.Extensions.Hosting;
using MQTTnet;
using S7.Net;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using S7.Net.Helper;
using Admin.NET.Application.Entity;
using WCS.Application.Service.Inventory.DTO;

namespace MyDemo.Application.Service.Plc;
/// <summary>
/// PLC
/// </summary>
[ApiDescriptionSettings(groups: ApplicationConst.GroupName, Order = 15)]
[AllowAnonymous]
public class MqttPlcService : IDynamicApiController 
{

    public static IMqttClient? _plcmqttClient = null;
    private readonly SqlSugarRepository<InventoryManagementEntity> _inventory;

    public MqttPlcService()
    {
        //_plcmqttClient = mqttClient;
    }


    private static S7.Net.Plc _plc;
    private static ConveyorSystemCommand _command;
    // Fix for CS0436: Resolve the conflict by explicitly specifying the namespace for VarType from S7.Net  
    // Fix for CS7036: Provide the required "varCount" parameter in the Read method call  
    /// <summary>
    /// 连接plc
    /// </summary>
    /// <param name="args"></param>
    /// <returns></returns>
    [ApiDescriptionSettings(Name = "SiemensPlc"), HttpPost]

    public  async Task SiemensPlc()
    {
        Console.WriteLine("西门子 PLC 与 MQTT 通信程序");
        Console.WriteLine("-----------------------------");

        try
        {
            // 初始化 PLC 连接  
            InitializePlcConnection();

            // 初始化 MQTT 连接  
            await InitializeMqttConnection();

            // Fix for CS0436 and CS1503: Explicitly specify the namespace for VarType to resolve conflicts  
            var value = _plc.Read(S7.Net.DataType.DataBlock, 1, 0, S7.Net.VarType.Word, 1);

            // 创建设备通信对象 (DB1, 第0台设备)  
            _command = new ConveyorSystemCommand(1, 0);

            // 启动数据采集和发布任务  
            var cancellationTokenSource = new CancellationTokenSource();
            var tasks = new List<Task>
           {
               StartReadingFromPlc(cancellationTokenSource.Token),

               StartListeningToMqtt(cancellationTokenSource.Token)
           };

            Console.WriteLine("系统已启动，按 Q 键退出...");
            while (Console.ReadKey(true).Key != ConsoleKey.Q) { }

            cancellationTokenSource.Cancel();
            await Task.WhenAll(tasks);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"发生错误: {ex.Message}");
        }
        finally
        {
            // 清理资源  
            _plc?.Close();
            if (_plcmqttClient?.IsConnected == true)
            {
                await _plcmqttClient.DisconnectAsync();
            }
        }
    }

    [HttpPost]
    public async Task<List<InventoryManagementEntity>> GetInventoryList(InventoryManagementDto Inventoryinput)
    {

        var list = await _inventory.AsQueryable()
         .WhereIF(!string.IsNullOrWhiteSpace(Inventoryinput.MaterialCode), u => u.MaterialCode == Inventoryinput.MaterialCode)
         .WhereIF(!string.IsNullOrWhiteSpace(Inventoryinput.Batch), u => u.Batch == Inventoryinput.Batch)
        .WhereIF(Enum.IsDefined(typeof(InventoryStatus), Inventoryinput.Status), u => u.Status.Equals(Inventoryinput.Status))
            .OrderBy(u => u.CreateTime, SqlSugar.OrderByType.Desc).ToListAsync();
        return list;
    }

    private static void InitializePlcConnection()
    {
        // 创建 PLC 连接对象 - 根据您的 PLC 类型修改  
        _plc = new S7.Net.Plc(CpuType.S71200, "192.168.0.12", 0, 1);

        Console.WriteLine("正在连接到 PLC...");

        try
        {
            _plc.Open();

            if (_plc.IsConnected)
            {
                Console.WriteLine("PLC 连接成功！");

                // 示例：读取 PLC 中的设备信息  
                try
                {
                    //数据类型  •Data Block）。（DB1）起始地址 字符串。读多长字符串。
                    var deviceInfo = _plc.Read(S7.Net.DataType.DataBlock, 1, 0, S7.Net.VarType.String, 10);


                    Console.WriteLine($"读取到的设备信息: {deviceInfo}");
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"读取设备信息时出错: {ex.Message}");
                }
            }
            else
            {
                throw new InvalidOperationException("无法连接到 PLC");
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"连接到 PLC 时出错: {ex.Message}");
            Console.WriteLine("请检查网络连接、PLC 地址和端口是否正确。");
        }
    }

    private static async Task InitializeMqttConnection()
    {
        var factory = new MqttClientFactory();
        _plcmqttClient = factory.CreateMqttClient();

        var options = new MqttClientOptionsBuilder()
            .WithClientId("plc-client-" + Guid.NewGuid().ToString())
            .WithTcpServer("localhost", 1883) // MQTT 服务器地址和端口
            .WithCleanSession()
            .Build();

        Console.WriteLine("正在连接到 MQTT 服务器...");
        await _plcmqttClient.ConnectAsync(options);

        if (_plcmqttClient.IsConnected)
        {
            Console.WriteLine("MQTT 连接成功！");

            // 订阅控制主题
            await _plcmqttClient.SubscribeAsync(new MqttTopicFilterBuilder()
                .WithTopic("plc/control/#")
                .Build());
        }
        else
        {
            throw new InvalidOperationException("无法连接到 MQTT 服务器");
        }
    }

    private static async Task StartReadingFromPlc(CancellationToken cancellationToken)
    {
        while (!cancellationToken.IsCancellationRequested)
        {
            try
            {
                if (_plc.IsConnected)
                {
                    // 从 PLC 读取数据
                    _command.ReadFromPlc(_plc);

                    // 转换为 JSON 格式
                    var payload = System.Text.Json.JsonSerializer.Serialize(_command);

                    // 发布到 MQTT
                    var message = new MqttApplicationMessageBuilder()
                        .WithTopic("plc/data")
                        .WithPayload(payload)
                        .WithQualityOfServiceLevel(MQTTnet.Protocol.MqttQualityOfServiceLevel.AtLeastOnce)
                        .Build();

                    await _plcmqttClient.PublishAsync(message, cancellationToken);

                    Console.WriteLine("已发布 PLC 数据到 MQTT");
                }
                else
                {
                    Console.WriteLine("PLC 连接已断开，尝试重新连接...");
                    _plc.Open();
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"读取 PLC 数据时出错: {ex.Message}");
            }

            await Task.Delay(1000, cancellationToken); // 每秒读取一次
        }
    }

    private static async Task StartListeningToMqtt(CancellationToken cancellationToken)
    {
        _plcmqttClient.ApplicationMessageReceivedAsync += e =>
        {
            try
            {
                Console.WriteLine($"收到 MQTT 消息: {e.ApplicationMessage.Topic}");

                // 处理控制消息
                if (e.ApplicationMessage.Topic.StartsWith("plc/hoist/"))
                {
                    var payload = System.Text.Encoding.UTF8.GetString(e.ApplicationMessage.Payload);

                    // 根据主题和负载更新 PLC 数据
                    UpdatePlcFromMqttMessage(e.ApplicationMessage.Topic, payload);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"处理 MQTT 消息时出错: {ex.Message}");
            }

            return Task.CompletedTask;
        };

        Console.WriteLine("正在监听 MQTT 消息...");

        while (!cancellationToken.IsCancellationRequested)
        {
            await Task.Delay(1000, cancellationToken);
        }
    }

    private static void UpdatePlcFromMqttMessage(string topic, string payload)
    {
        try
        {
            // 根据具体需求解析消息并更新 PLC
            // 示例：更新 Command
            if (topic.EndsWith("/command"))
            {
                if (short.TryParse(payload, out short commandValue))
                {
                    _command.Command = commandValue;
                    _command.WriteToPlc(_plc);
                    Console.WriteLine($"已更新 Command 为: {commandValue}");
                }
            }

            // 可以添加更多的处理逻辑...
        }
        catch (Exception ex)
        {
            Console.WriteLine($"更新 PLC 时出错: {ex.Message}");
        }
    }
}
